package com.seed.bpmn.provider.camunda.service;

import com.seed.bpmn.engine.IBPMNRepositoryService;
import com.seed.bpmn.engine.common.constants.BpmnConstants;
import com.seed.bpmn.engine.common.util.Empty;
import com.seed.bpmn.engine.common.util.Page;
import com.seed.bpmn.engine.dto.FlowDTO;
import com.seed.bpmn.engine.dto.NodeDTO;
import com.seed.bpmn.engine.dto.ProcessDefinitionDTO;
import com.seed.bpmn.provider.camunda.common.util.CamundaUtil;
import com.seed.bpmn.provider.camunda.common.util.DTOUtil;
import net.sf.json.JSONObject;
import org.apache.commons.io.FilenameUtils;
import org.camunda.bpm.engine.HistoryService;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.TaskService;
import org.camunda.bpm.engine.history.HistoricProcessInstance;
import org.camunda.bpm.engine.impl.RepositoryServiceImpl;
import org.camunda.bpm.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.camunda.bpm.engine.impl.pvm.PvmActivity;
import org.camunda.bpm.engine.impl.pvm.PvmTransition;
import org.camunda.bpm.engine.impl.pvm.process.ActivityImpl;
import org.camunda.bpm.engine.impl.pvm.process.ScopeImpl;
import org.camunda.bpm.engine.repository.Deployment;
import org.camunda.bpm.engine.repository.DeploymentBuilder;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.engine.repository.ProcessDefinitionQuery;
import org.camunda.bpm.engine.task.Task;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.UserTask;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipInputStream;

import static com.seed.bpmn.engine.common.constants.BpmnConstants.*;

/**
 * <p>流程模版服务camunda实现</p>
 *
 * @author cscz89@126.com
 */
@Service("bpmnRepositoryService")
public class BPMNRepositoryServiceImpl implements IBPMNRepositoryService {

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private HistoryService historyService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private CamundaUtil camundaUtil;

    @Override
    public ProcessDefinitionDTO deploy(String name, String fileName, String category, InputStream inputStream) throws Exception {
        try{
            //流程部署（发布）
            DeploymentBuilder b = this.repositoryService.createDeployment().name(name);
            Deployment d = null;

            String extension = FilenameUtils.getExtension(fileName);
            if("zip".equals(extension) || "bar".equals(extension)|| "rar".equals(extension)){
                ZipInputStream zip = new ZipInputStream(inputStream);
                d = b.addZipInputStream(zip).deploy();
            } else {
                d = b.addInputStream(fileName, inputStream).deployWithResult();
            }

            //获取发布成功的流程模版
            ProcessDefinition pd = this.repositoryService.createProcessDefinitionQuery().deploymentId(d.getId()).singleResult();
            return DTOUtil.getProcessDefinitionDTO(pd);
        }catch (Exception e){
            throw new Exception(e.getMessage());
        }
    }

    @Override
    public ProcessDefinitionDTO getProcessDefinition(String id) throws Exception {
        try{
            ProcessDefinition pd = this.repositoryService.createProcessDefinitionQuery().processDefinitionId(id).singleResult();
            return DTOUtil.getProcessDefinitionDTO(pd);
        }catch (Exception e){
            throw new Exception(e.getMessage());
        }
    }

    @Override
    public List<ProcessDefinitionDTO> findProcessDefinitionByPage(String name, String key, Page page) throws Exception {
        try{
            //创建流程模版查询对象
            ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();

            //----封装查询条件----------
            if (!Empty.isEmpty(name)) {
                query.processDefinitionNameLike("%" + name + "%");
            }

            if (!Empty.isEmpty(key)) {
                query.processDefinitionKey(key);
            }
            //--------------------------

            //排序设置
            query.orderByProcessDefinitionKey().asc().orderByProcessDefinitionVersion().desc();

            //分页信息设置
            page.setTotalObjects((int) query.count());


            int start = page.getFirstResult();
            int end = page.getPageIndex() * page.getPageSize();

            List<ProcessDefinition> dlist = query.listPage(start, end);

            List<ProcessDefinitionDTO> list = new ArrayList<>();
            for (ProcessDefinition d : dlist) {
                list.add(DTOUtil.getProcessDefinitionDTO(d));
            }

            return list;
        }catch (Exception e){
            throw new Exception(e.getMessage());
        }
    }

    @Override
    public ProcessDefinitionDTO findProcessDefinitionByKey(String key) throws Exception {
        try{
            //根据KEY获取最新的激活状态的流程定义
            ProcessDefinitionQuery query = this.repositoryService.createProcessDefinitionQuery();
            List<ProcessDefinition> list = query.processDefinitionKey(key).active()
                .orderByProcessDefinitionVersion().desc().listPage(0, 1);
            if (!Empty.isEmpty(list)) {
                return DTOUtil.getProcessDefinitionDTO(list.get(0));
            } else {
                return null;
            }
        }catch (Exception e){
            throw new Exception(e.getMessage());
        }
    }

    @Override
    public NodeDTO getNodeByProcessDefinitionKey(String processDefinitionKey, String nodeId) throws Exception {
        try{
            NodeDTO node = null;
            if (Empty.isNotEmpty(processDefinitionKey)) {
                ProcessDefinitionDTO d = this.findProcessDefinitionByKey(processDefinitionKey);
                if (Empty.isNotEmpty(d)) {
                    node = this.getNodeByProcessDefinitionId(d.getId(), nodeId);
                }
            }
            return node;
        }catch (Exception e){
            throw new Exception(e.getMessage());
        }
    }

    @Override
    public List<NodeDTO> getNodeByProcessDefinitionId(String processDefinitionId) throws Exception {
        try{
            BpmnModelInstance model = repositoryService.getBpmnModelInstance(processDefinitionId);
            Collection<UserTask> list = model.getModelElementsByType(UserTask.class);

            //流程定义实体类,能得到流程的环节
            RepositoryServiceImpl repositoryServiceImpl = (RepositoryServiceImpl) repositoryService;
            ProcessDefinitionEntity pde = (ProcessDefinitionEntity) repositoryServiceImpl.getDeployedProcessDefinition(processDefinitionId);

            List<NodeDTO> nodes = new ArrayList<NodeDTO>();
            for(UserTask t : list){
                NodeDTO node = new NodeDTO();
                node.setId(t.getId());
                node.setName(t.getName());

                ActivityImpl a = pde.findActivity(t.getId());
                String documentation = (String)a.getProperty(BpmnConstants.ELEMENT_DOCUMENTATION);

                if(Empty.isNotEmpty(documentation)){
                    JSONObject data = JSONObject.fromObject(documentation);
                    //封装流程节点配置
                    node = DTOUtil.getNodeDTO(data, node);
                }
                nodes.add(node);
            }
            return nodes;
        }catch (Exception e){
            throw new Exception(e.getMessage());
        }
    }

    @Override
    public NodeDTO getNodeByProcessDefinitionId(String processDefinitionId, String nodeId) throws Exception {
        try{
            RepositoryServiceImpl repositoryServiceImpl = (RepositoryServiceImpl) repositoryService;

            //流程定义实体类,能得到流程的环节
            ProcessDefinitionEntity pde = (ProcessDefinitionEntity) repositoryServiceImpl.getDeployedProcessDefinition(processDefinitionId);

            NodeDTO node = new NodeDTO();
            String documentation;

            if (Empty.isNotEmpty(nodeId)) {
                //通过流程环节id，可以得到流程环节
                ActivityImpl a = pde.findActivity(nodeId);
                documentation = (String) a.getProperty(ELEMENT_DOCUMENTATION);

                node.setId(a.getId());
                node.setName(a.getProperty(ATTRIBUTE_NAME) + "");
            } else {
                //通过流程环节id，可以得到流程环节
                ActivityImpl activity = pde.findActivity(ELEMENT_START_EVENT_ID);

                //得到流程环节出去的线
                List<PvmTransition> pts = activity.getOutgoingTransitions();
                PvmTransition t = pts.get(0);
                PvmActivity d = t.getDestination();

                //获取流程节点documentation上配置的参数
                documentation = (String) d.getProperty(ELEMENT_DOCUMENTATION);

                node.setId(d.getId());
                node.setName(d.getProperty(ATTRIBUTE_NAME) + "");
            }

            if (Empty.isNotEmpty(documentation)) {
                JSONObject data = JSONObject.fromObject(documentation);
                //封装流程节点配置
                node = DTOUtil.getNodeDTO(data, node);
            }

            return node;
        }catch (Exception e){
            throw new Exception(e.getMessage());
        }
    }

    @Override
    public NodeDTO getNodeByTaskId(String taskId) throws Exception {
        try{
            Task t = this.taskService.createTaskQuery().taskId(taskId).singleResult();
            if (Empty.isNotEmpty(t)) {
                return this.getNodeByProcessDefinitionId(t.getProcessDefinitionId(),
                    t.getTaskDefinitionKey());
            } else {
                return null;
            }
        }catch (Exception e){
            throw new Exception(e.getMessage());
        }
    }

    @Override
    public List<FlowDTO> getNextTransitions(String processDefinitionId, String taskId) throws Exception {
        try{
            List<FlowDTO> flows = new ArrayList<FlowDTO>();
            String nodeId = ELEMENT_START_EVENT_ID;
            //当任务id不为空时
            if (Empty.isNotEmpty(taskId)) {
                Task task = this.taskService.createTaskQuery().taskId(taskId).singleResult();
                nodeId = task.getTaskDefinitionKey();
                processDefinitionId = task.getProcessDefinitionId();
            }

            if (Empty.isNotEmpty(processDefinitionId)) {
                //流程定义实体类,能得到流程的环节
                RepositoryServiceImpl repositoryServiceImpl = (RepositoryServiceImpl) this.repositoryService;
                ProcessDefinitionEntity pde = (ProcessDefinitionEntity) repositoryServiceImpl.getDeployedProcessDefinition(processDefinitionId);

                //通过流程环节id，可以得到流程环节
                ActivityImpl activity = pde.findActivity(nodeId);

                ScopeImpl scope = activity.getFlowScope();

                //得到流程环节出去的线
                List<PvmTransition> pts = activity.getOutgoingTransitions();
                if(scope instanceof ActivityImpl){
                    pts = ((ActivityImpl)scope).getOutgoingTransitions();
                }

                //封装下一流程相关数据
                camundaUtil.getNextTransitionsProperties(taskId, pts, flows, null);
            }
            return flows;
        }catch (Exception e){
            throw new Exception(e.getMessage());
        }
    }

    @Override
    public List<FlowDTO> getNextTransitions(String processDefinitionId, String taskId, String nodeId) throws Exception {
        return null;
    }

    @Override
    public InputStream getProcessDefinitionXml(String processDefinitionId) throws Exception {
        return this.getProcessDefinitionById(processDefinitionId, "xml");
    }

    @Override
    public InputStream getProcessDefinitionImage(String processDefinitionId) throws Exception {
        return this.getProcessDefinitionById(processDefinitionId, "image");
    }

    @Override
    public InputStream getProcessDefinitionById(String processDefinitionId, String type) throws Exception {
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId);
        ProcessDefinition processDefinition = processDefinitionQuery.singleResult();
        String resourceName = "";
        if ("image".equalsIgnoreCase(type)) {
            resourceName = processDefinition.getDiagramResourceName();
        } else if ("xml".equalsIgnoreCase(type)) {
            resourceName = processDefinition.getResourceName();
        }
        InputStream is = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), resourceName);
        return is;
    }

    @Override
    public InputStream getProcessDefinitionByProcessInstanceId(String processInstanceId, String type) throws Exception {
        HistoricProcessInstance p = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        return this.getProcessDefinitionById(p.getProcessDefinitionId(), type);
    }

    @Override
    public void deleteProcessDefinition(List<String> ids) throws Exception {
        for (String id : ids) {
            repositoryService.deleteDeployment(id, true);
        }
    }

    @Override
    public void updateProcessDefinition(List<String> ids, int type) throws Exception {
        for (String id : ids) {
            if (type == 1) {
                repositoryService.activateProcessDefinitionById(id);
            } else {
                repositoryService.suspendProcessDefinitionById(id);
            }
        }
    }

    @Override
    public List<Map<String, Object>> traceProcess(String processInstanceId) throws Exception {
        return new ArrayList<Map<String, Object>>();
    }

}
